avatar

目录
261 - Type traits

261 - Type Traits

https://gemini.google.com/u/2/app/d94bf6b5e1f2a4ca

好的,学员你好!我是你的 C++ 老师。很高兴能带你进入 C++ 的世界。C++ 功能强大但也有些复杂,别担心,我会用最清晰、最有效的方法,一步步带你学习。

今天我们要学习的是 C++ 中的一个现代特性:类型特性 (Type Traits)


1. 标题和摘要

标题: C++ 深入浅出:掌握编译时类型检查的利器——类型特性 (Type Traits)

摘要:

本节课我们将学习 C++ 中的类型特性 (Type Traits)。这是一种在编译时 (compile time) 查询类型信息的技术,尤其在编写模板 (template) 代码时非常有用。我们将探讨如何使用 std::is_integral 等类型特性,了解 ::value 和 C++17 引入的 _v 后缀语法,并学习如何结合静态断言 (static assertion) 在编译阶段就对类型进行检查和约束,从而写出更安全、更健壮的模板代码。


2. 详细解释

让我们一步步来深入理解类型特性:

  • 什么是类型特性 (Type Traits)?

    • 想象一下,在你的 C++ 代码编译的时候,你就能像“提问”一样,去问编译器:“嘿,这个类型 T 是不是一个整数类型?”或者“这个类型 T 是不是一个指针?”。类型特性就是 C++ 标准库提供的一套工具,让你能够在代码 编译期间 就获取到一个类型的各种特征信息。
    • 它操作的对象是 类型 本身,而不是程序运行时产生的具体值 (value)。这一点非常重要!
    • 要使用类型特性,通常需要包含头文件 <type_traits>
  • 为什么需要类型特性?

    • 最主要的用途是在编写模板 (template) 代码时。模板允许我们编写通用的代码,可以处理多种不同的数据类型。但有时,我们希望对传入模板的类型进行限制,或者根据类型的不同特性执行不同的代码逻辑。
    • 例如,你可能写了一个只应该处理数字类型的模板函数,或者你想为一个函数提供针对整数类型和浮点类型的不同实现。类型特性就能帮助你在编译时就判断传入的类型是否符合要求,或者它具体是什么类型的“近亲”(比如是不是整数、是不是浮点数、是不是类等等)。
  • 基本用法:::value

    • 类型特性通常是一个定义在 std 命名空间下的模板结构体(struct)。要获取它的检查结果(通常是一个布尔值 (boolean),即 truefalse),你需要访问它内部的静态成员 value
    • 语法是:std::特性名称<要检查的类型>::value
    • 示例 1:检查 int 是否为整数类型

      C++

      Code
      1
      2
      3
      4
      5
      6
      7
      8
      9
      #include <type_traits>
      #include <iostream>

      int main() {
      // std::is_integral<int>::value 会返回 true
      std::cout << "Is int an integral type? "
      << std::is_integral<int>::value << std::endl;
      return 0;
      }
    • 示例 2:检查 double 是否为整数类型

      C++

      Code
      1
      2
      3
      4
      5
      6
      7
      8
      9
      #include <type_traits>
      #include <iostream>

      int main() {
      // std::is_integral<double>::value 会返回 false
      std::cout << "Is double an integral type? "
      << std::is_integral<double>::value << std::endl;
      return 0;
      }
    • 重要陷阱:不能对变量名(值)使用 如下代码是 错误 的:

      C++

      Code
      1
      2
      3
      int a = 10;
      // 错误!不能把变量 a 传给类型模板参数
      // bool result = std::is_integral<a>::value;

      类型特性模板的尖括号 <> 里需要的是一个 类型,比如 int, double, std::string 或者模板参数 T,而不是一个变量名 a。如果你想检查变量 a 的类型,应该这样做:

      C++

      Code
      1
      2
      3
      4
      5
      6
      #include <type_traits>
      int a = 10;
      // 使用 decltype(a) 获取变量 a 的类型 int
      bool result = std::is_integral<decltype(a)>::value; // 正确
      // 或者直接用已知的类型
      bool result_direct = std::is_integral<int>::value; // 正确
    • 打印 true/false: 为了让输出更直观地显示 truefalse 而不是 10,可以在输出前使用 std::boolalpha

      C++

      Code
      1
      2
      3
      #include <iostream>
      std::cout << std::boolalpha; // 设置输出流以打印 true/false
      std::cout << true << std::endl; // 输出 "true"
  • 常见的类型特性

    • C++ 标准库在 <type_traits> 中提供了大量的类型特性,远不止 is_integral。就像一个“宝库”!
    • 基础类别检查:
      • std::is_integral<T>: 是否为整数类型 (bool, char, short, int, long, long long 等)。
      • std::is_floating_point<T>: 是否为浮点类型 (float, double, long double)。
      • std::is_array<T>: 是否为数组类型。
      • std::is_enum<T>: 是否为枚举类型。
      • std::is_union<T>: 是否为联合体类型。
      • std::is_class<T>: 是否为类类型(struct 或 class,但不包括 union)。
      • std::is_function<T>: 是否为函数类型。
      • std::is_pointer<T>: 是否为指针类型。
      • std::is_reference<T>: 是否为引用类型 (左值引用 & 或右值引用 &&)。
      • std::is_void<T>: 是否为 void 类型。
      • 等等…
    • 复合类别检查:
      • std::is_fundamental<T>: 是否为基础类型 (算术类型、voidstd::nullptr_t)。
      • std::is_arithmetic<T>: 是否为算术类型 (整数或浮点类型)。
      • std::is_scalar<T>: 是否为标量类型 (算术、指针、成员指针、枚举、std::nullptr_t)。
      • std::is_object<T>: 是否为对象类型 (除了函数、引用、void 之外的任何类型)。
      • std::is_compound<T>: 是否为复合类型 (非基础类型)。
    • 你可以在 C++ 参考文档(如 cppreference.com)上查找 <type_traits>,会看到一个非常长的列表,可以满足各种检查需求。
  • 更简洁的语法:_v 后缀 (C++17)

    • 每次写 ::value 有点繁琐。从 C++17 开始,标准库为绝大多数返回布尔值 (boolean) 的类型特性提供了一个更简洁的写法,即在特性名称后面加上 _v(代表 “value”)。
    • std::特性名称_v<要检查的类型> 就等价于 std::特性名称<要检查的类型>::value
    • 示例:

      C++

      Code
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      #include <type_traits>
      #include <iostream>

      int main() {
      std::cout << std::boolalpha;
      // 使用 _v 后缀
      std::cout << "Is int an integral type? "
      << std::is_integral_v<int> << std::endl; // 输出 true
      std::cout << "Is double an integral type? "
      << std::is_integral_v<double> << std::endl; // 输出 false
      std::cout << "Is float a floating point type? "
      << std::is_floating_point_v<float> << std::endl; // 输出 true
      return 0;
      }
    • _v 实际上是一个 变量模板 (variable template),它是 ::value 的一个方便的别名 (alias)

    • 注意: 这个 _v 后缀是 C++17 标准引入的。如果你的编译器或项目设置使用的 C++ 标准低于 C++17,就不能使用 _v,只能用 ::value
  • 类型特性与 static_assert 的结合:编译时约束

    • static_assert静态断言 (static assertion))是 C++ 提供的一个非常有用的特性,它允许你在 编译时 (compile time) 检查某个条件是否为真。
    • 语法:static_assert(编译时条件, "如果条件为假时显示的错误信息");
    • 工作方式:
      • 如果 编译时条件 在编译时计算结果为 truestatic_assert 什么也不做,编译继续。
      • 如果 编译时条件 在编译时计算结果为 false,编译器会 立即停止编译,并显示你提供的 错误信息
    • 杀手级应用:约束模板参数! 我们可以用类型特性作为 static_assert 的条件,来强制模板函数或类只能接受特定类型的参数。
    • 示例:print_number 函数只接受整数类型

      C++

      Code
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      #include <type_traits>
      #include <iostream>
      #include <string> // 为了用 string 做反例

      template <typename T>
      void print_number(T n) {
      // 在编译时检查 T 是否是整数类型
      static_assert(std::is_integral_v<T>, // 条件:T 必须是整数
      "print_number function can only be called with integral types."); // 错误信息

      std::cout << "Number: " << n << std::endl;
      }

      int main() {
      int integer_num = 7;
      print_number(integer_num); // OK: int 是整数类型,编译通过

      // double double_num = 3.14;
      // print_number(double_num); // 编译错误!double 不是整数类型,static_assert 失败

      // std::string text = "hello";
      // print_number(text); // 编译错误!std::string 不是整数类型,static_assert 失败

      return 0;
      }

      如果你取消上面 double_numtext 相关行的注释并尝试编译,编译器会报错,并且错误信息会包含你写在 static_assert 里的那句话 “print_number function can only be called with integral types.”。这极大地提高了模板代码的安全性和易用性,用户在编译阶段就能发现错误用法。

  • 类型特性与 Lambda 函数

    • 从 C++20 开始,Lambda 表达式也可以拥有模板参数列表(更早的版本可以用泛型 lambda,但显式模板参数提供了更多控制)。这使得我们也可以在 Lambda 内部使用 static_assert 和类型特性来约束参数类型。
    • 示例:只接受整数的加法 Lambda

      C++

      Code
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      #include <type_traits>
      #include <iostream>

      int main() {
      // 定义一个带模板参数的 Lambda
      auto add = []<typename T>(T a, T b) {
      // 约束 T 必须是整数类型
      static_assert(std::is_integral_v<T>, "Lambda 'add' can only be called with integral types.");
      return a + b;
      };

      int x = 5, y = 10;
      std::cout << "Sum (int): " << add(x, y) << std::endl; // OK

      // double d1 = 1.5, d2 = 2.5;
      // std::cout << "Sum (double): " << add(d1, d2) << std::endl; // 编译错误!static_assert 失败

      return 0;
      }

      这展示了即使是对于 Lambda 这种便捷的函数对象,我们也能利用类型特性和 static_assert 实现编译时的类型安全检查。

总结一下,类型特性是 C++ 中一个强大的元编程 (metaprogramming) 工具,它允许我们在编译时就了解和判断类型的属性,特别是在与模板和 static_assert 结合使用时,能极大地增强代码的健壮性和开发效率。


3. 代码示例

下面是一个更完整的代码示例,演示了本节课讨论的几个关键点:

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <type_traits> // 引入类型特性库
#include <string> // 引入 string 类型用于测试

// 模板函数:打印数字,但只允许整数类型
template <typename T>
void print_integral_number(T n) {
// 使用 static_assert 和 is_integral_v 进行编译时检查
static_assert(std::is_integral_v<T>,
"Error: print_integral_number requires an integral type.");
std::cout << "[print_integral_number] Value: " << n << std::endl;
}

int main() {
// 设置 cout 输出 true/false 而不是 1/0
std::cout << std::boolalpha;

// 1. 使用 ::value 语法检查类型特性
std::cout << "1. Using ::value syntax:" << std::endl;
std::cout << " Is int integral? " << std::is_integral<int>::value << std::endl; // true
std::cout << " Is double integral? " << std::is_integral<double>::value << std::endl; // false
std::cout << " Is int floating point? " << std::is_floating_point<int>::value << std::endl; // false

std::cout << "\n---------------------------\n" << std::endl;

// 2. 使用 _v 后缀语法 (C++17 及以后)
std::cout << "2. Using _v suffix (C++17):" << std::endl;
std::cout << " Is int integral? " << std::is_integral_v<int> << std::endl; // true
std::cout << " Is float floating point? " << std::is_floating_point_v<float> << std::endl; // true
std::cout << " Is char* a pointer? " << std::is_pointer_v<char*> << std::endl; // true

std::cout << "\n---------------------------\n" << std::endl;

// 3. 使用带有 static_assert 的模板函数
std::cout << "3. Using template function with static_assert:" << std::endl;
int my_int = 100;
unsigned long my_ulong = 200UL;
char my_char = 'A'; // char 也是整数类型

print_integral_number(my_int); // OK
print_integral_number(my_ulong); // OK
print_integral_number(my_char); // OK (char is integral)

// 下面的调用会导致编译错误,因为类型不满足 static_assert 条件
// double my_double = 3.14;
// print_integral_number(my_double); // Compile Error!

// std::string my_string = "Hello";
// print_integral_number(my_string); // Compile Error!

std::cout << "\n---------------------------\n" << std::endl;

// 4. 演示带模板参数和 static_assert 的 Lambda (C++20 风格)
std::cout << "4. Using Lambda with template parameter and static_assert:" << std::endl;
auto multiply_if_arithmetic = []<typename T>(T a, T b) {
// 检查 T 是否是算术类型 (整数或浮点数)
static_assert(std::is_arithmetic_v<T>, "Lambda requires arithmetic types for multiplication.");
return a * b;
};

int result_int = multiply_if_arithmetic(5, 6);
std::cout << " Lambda multiplication (int): " << result_int << std::endl; // OK

double result_double = multiply_if_arithmetic(2.5, 4.0);
std::cout << " Lambda multiplication (double): " << result_double << std::endl; // OK

// 下面的调用会导致编译错误
// std::string s1 = "a", s2 = "b";
// multiply_if_arithmetic(s1, s2); // Compile Error! string is not arithmetic

return 0;
}

你可以尝试编译并运行这段代码。注意观察输出,并尝试取消那些会导致编译错误的行的注释,看看编译器会给出什么样的错误信息(通常会包含 static_assert 失败和你提供的消息)。


4. QA 闪卡 (Flash Cards)

卡片 1

问: 类型特性 (Type Traits) 是什么?

答: 是 C++ 标准库提供的一套工具,用于在 编译时 (compile time) 查询一个 类型 的各种属性和特征。

卡片 2

问: 如何检查一个类型 T 是否是整数类型?给出两种语法。

答:

  1. std::is_integral<T>::value
  2. std::is_integral_v<T> (需要 C++17 或更高版本)

卡片 3

问: std::is_integral_v 和 std::is_integral::value 有什么关系?

答: _v 版本是 C++17 引入的变量模板 别名 (alias),它是 ::value 版本的简写形式,两者功能等价。

卡片 4

问: static_assert 的作用是什么?它发生在哪个阶段?

答: static_assert 用于在 编译时 (compile time) 进行断言检查。如果条件为假,编译会失败并显示指定的错误信息。

卡片 5

问: 为什么类型特性经常和 static_assert 一起用在模板代码中?

答: 为了在编译时就强制约束模板参数必须满足某些类型要求(例如,必须是整数,必须是指针等),从而提高代码的健壮性和安全性,并提供清晰的编译期错误提示。

卡片 6

问: 使用类型特性需要包含哪个头文件?

答:


5. 常见误解或易犯错误

  1. 对变量(值)而非类型使用类型特性:

    • 错误: int x = 5; auto check = std::is_integral<x>::value;
    • 正确: int x = 5; auto check = std::is_integral<decltype(x)>::value;auto check = std::is_integral<int>::value;
    • 原因: 类型特性模板的 <> 中需要的是一个类型,而不是一个变量名。decltype(x) 可以获取变量 x 的类型。
  2. 忘记写 ::value_v 后缀:

    • 错误 (在需要布尔值的地方): if (std::is_integral<T>) { ... }static_assert(std::is_integral<T>, "...");
    • 正确: if (std::is_integral<T>::value) { ... }if (std::is_integral_v<T>) { ... }
    • 原因: std::is_integral<T> 本身是一个类型(结构体),而不是它所代表的布尔结果。你需要访问它的 value 成员或使用 _v 变量模板来获取那个 truefalse 的值。 (注:在某些高级技术如 SFINAE 中,类型本身可以被使用,但这超出了初学者的范围)。
  3. 在低于 C++17 的环境中使用 _v 后缀:

    • 错误: 在 C++11 或 C++14 项目中使用了 std::is_integral_v<T>
    • 正确: 在这些旧标准中,只能使用 std::is_integral<T>::value
    • 原因: _v 系列变量模板是 C++17 标准才加入的。
  4. 忘记包含 <type_traits> 头文件:

    • 错误: 直接使用 std::is_integral 而没有 #include <type_traits>
    • 正确: 总是确保在使用前包含了相应的头文件。
    • 原因: 编译器不认识未声明的标识符。
  5. 混淆 static_assert 和运行时 assert

    • 误解: 认为 static_assert 是在程序运行时进行检查。
    • 正确: static_assert编译时 检查,如果失败,程序根本无法成功编译。运行时的检查通常使用 <cassert> 头文件中的 assert() 宏。
    • 原因: 两者目的和作用时机完全不同。static_assert 用于保证编译时的类型约束和常量表达式的正确性。

6. 编码练习

现在,轮到你来实践一下了!下面有一个模板函数 process_data,我们希望它只接受 浮点类型 (float, double, long double) 的参数。请你在函数体内部,使用 static_assert 和我们今天学到的类型特性(特别是用于检查浮点类型的那个)来添加这个编译时检查。

C++

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
#include <type_traits> // 确保包含了头文件
#include <vector> // 假设我们处理的是浮点数向量

template <typename T>
void process_data(const std::vector<T>& data) {
// TODO: 在这里添加一个 static_assert 检查
// 要求:确保类型 T 必须是一个浮点类型 (floating point type)。
// 如果不是,编译时应该报错,并提示 "process_data requires a floating point type."
// 请使用 _v 后缀的语法。


// 函数的其余部分(这里只是个示例)
std::cout << "Processing data of type: ";
if constexpr (std::is_same_v<T, float>) { // is_same_v 也是一个类型特性
std::cout << "float";
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "double";
} else if constexpr (std::is_same_v<T, long double>) {
std::cout << "long double";
}
std::cout << ". Vector size: " << data.size() << std::endl;
// ... 实际的数据处理逻辑 ...
}

int main() {
std::vector<double> double_vec = {1.1, 2.2, 3.3};
process_data(double_vec); // 这个调用应该是 OK 的

std::vector<float> float_vec = {4.4f, 5.5f, 6.6f};
process_data(float_vec); // 这个调用也应该是 OK 的

// 下面的调用应该因为你的 static_assert 而导致编译失败
// std::vector<int> int_vec = {7, 8, 9};
// process_data(int_vec); // Compile Error should happen here!

return 0;
}

请你将上面代码中 // TODO: 部分替换成正确的 static_assert 语句。完成后,请将你添加的那行代码回复给我。我会检查你的答案,并给出评分和反馈。加油!

homework:

c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <iostream>

#include <type_traits>  // 确保包含了头文件

#include <vector>       // 假设我们处理的是浮点数向量



template <typename T>

void process_data(const std::vector<T>& data) {

    // TODO: 在这里添加一个 static_assert 检查

    // 要求:确保类型 T 必须是一个浮点类型 (floating point type)。

    // 如果不是,编译时应该报错,并提示 "process_data requires a floating point

    // type." 请使用 _v 后缀的语法。



    static_assert(std::is_floating_point_v<T>,

                  "process_data requires a floating point");

    // 函数的其余部分(这里只是个示例)

    std::cout << "Processing data of type: ";

    if constexpr (std::is_same_v<T, float>) {  // is_same_v 也是一个类型特性

        std::cout << "float";

    } else if constexpr (std::is_same_v<T, double>) {

        std::cout << "double";

    } else if constexpr (std::is_same_v<T, long double>) {

        std::cout << "long double";

    }

    std::cout << ". Vector size: " << data.size() << std::endl;

    // ... 实际的数据处理逻辑 ...

}



int main() {

    std::vector<double> double_vec = {1.1, 2.2, 3.3};

    process_data(double_vec);  // 这个调用应该是 OK 的



    std::vector<float> float_vec = {4.4f, 5.5f, 6.6f};

    process_data(float_vec);  // 这个调用也应该是 OK 的



    // 下面的调用应该因为你的 static_assert 而导致编译失败

    // std::vector<int> int_vec = {7, 8, 9};

    // process_data(int_vec); // Compile Error should happen here!



    return 0;

}

太棒了!你的答案非常正确。


7. 评分、答案与解释

评分: 5 / 5 (优秀!) ⭐⭐⭐⭐⭐

你的答案:

C++

Code
1
2
static_assert(std::is_floating_point_v<T>,
"process_data requires a floating point");

标准答案与解释:

你给出的答案 static_assert(std::is_floating_point_v<T>, "process_data requires a floating point"); 是完全正确的!

  • std::is_floating_point_v<T>:这正是用来检查类型 T 是否为浮点类型(即 float, doublelong double)的正确类型特性,并且你正确地使用了 C++17 的 _v 后缀。
  • static_assert(...):你将这个检查放在了 static_assert 中,确保了这个检查是在 编译时 进行的。
  • 错误信息:你提供的错误信息 "process_data requires a floating point" 虽然和我要求的 "process_data requires a floating point type." 在文字上稍微有点出入(少了 “type”),但这完全不影响功能的正确性,它仍然清晰地表达了约束条件。在实际开发中,这样的信息也是完全可以接受的。

工作原理回顾:

当编译器尝试为 process_data(double_vec) (T=double) 或 process_data(float_vec) (T=float) 生成代码时:

  1. std::is_floating_point_v<double>std::is_floating_point_v<float> 的计算结果为 true
  2. static_assert 的条件满足,编译继续进行。

当编译器尝试为 process_data(int_vec) (T=int) 生成代码时(如果你取消注释):

  1. std::is_floating_point_v<int> 的计算结果为 false
  2. static_assert 的条件不满足,编译立即失败,并显示错误信息 "process_data requires a floating point"

你做得非常好,准确地理解并运用了 static_assert 和类型特性来约束模板参数!


8. 建议与扩展问题

恭喜你掌握了类型特性与 static_assert 的基本用法!为了进一步加深理解和拓宽知识面,你可以尝试探索以下内容:

  1. 探索更多类型特性:

    • 浏览 C++ 参考文档(如 cppreference.com)中的 <type_traits> 库。尝试使用其他的特性,比如:
      • std::is_pointer_v<T>:检查 T 是否为指针。
      • std::is_reference_v<T>:检查 T 是否为引用。
      • std::is_class_v<T>:检查 T 是否为类或结构体。
      • std::is_unsigned_v<T>:检查 T 是否为无符号整数类型。
      • std::is_base_of_v<Base, Derived>:检查 Base 是否是 Derived 的基类(或相同类型)。
      • std::is_same_v<T, U>:检查 T 和 U 是否是同一类型(你在示例代码的 if constexpr 中已经看到了它的应用)。
  2. 组合类型特性:

    • 你可以使用逻辑运算符(&&, ||, !)来组合多个类型特性进行更复杂的检查。例如,如何检查一个类型 T 既是整数类型又是无符号类型
      • 提示:std::is_integral_v<T> && std::is_unsigned_v<T>
  3. 了解 if constexpr (C++17):

    • 你在示例代码中已经看到了 if constexpr。它允许你根据编译时的条件(通常是类型特性)来选择性地编译代码块。这是类型特性非常有用的一个应用场景,可以让你为不同的类型提供不同的实现,而不会产生不必要的代码或编译错误。
  4. 了解 C++20 Concepts:

    • C++20 引入了概念 (Concepts),这是一种更高级、更直接、更易读的方式来约束模板参数。很多标准库的概念(如 std::integral, std::floating_point)底层可能就是用类型特性来实现的,但它们提供了更好的语法和编译器错误信息。如果你使用的环境支持 C++20,非常建议学习一下 Concepts,它是现代 C++ 模板编程的重要特性。

扩展思考题:

假设你希望 process_data 函数不仅能处理浮点类型,也能处理 整数类型(即任何算术类型都可以)。你会如何修改那个 static_assert 语句来实现这个要求?(提示:在 <type_traits> 中寻找一个能代表“算术类型”的特性。)

继续努力,C++ 的世界还有很多有趣的东西等着你去发现!


评论